import re
import threading
import urllib2

import jsonfetch

class PrimarySet(object):
  def __init__(self, type, entries, terms):
    self.type = type
    self.entries = entries
    self.terms = terms

  def getTerm(self, type):
    for term in self.terms:
      if term.type == type:
        return term
    return None

  def getEntries(self, type):
    for entry in self.entries:
      if entry.type == type:
        yield entry

  def toString(self, indent=0):
    s = '%*s<PrimarySet type=%s>\n' % (indent, '', self.type)
    s += '\n'.join(x.toString(indent + 2) for x in self.entries + self.terms)
    s += '%*s</PrimarySet>\n' % (indent, '')
    return s

  def renderAllDefinitions(self, row_limit=3, col_limit=420):
    if self.type != 'headword':
      return []
    definitions = []
    definition_count = 0
    for entry in self.entries:
      if isinstance(entry, PrimarySet) and entry.type == 'meaning':
        definition_count += 1
        if len(definitions) < row_limit:
          definitions.append(
              '\x02%d.\x02 %s'
               % (definition_count, entry.renderDefinition(limit=col_limit)))
    if definition_count > row_limit:
      definitions[-1] = 'Plus %d more definitions.' % (
          definition_count - row_limit + 1)
    return definitions

  def renderDefinition(self, limit=450):
    if self.type != 'meaning':
      return None
    term = self.getTerm('text')
    if not term:
      return None
    result = term.text
    # Each example contributes len(text) + 2 bytes to the length of the result
    # line. The first word bears the cost of the parentheses, while each
    # subsequent word bears the cost of a semi-colon and space. There is also an
    # extra space before the parentheses.
    examples = []
    example_len = 1
    for entry in self.getEntries('example'):
      term = entry.getTerm('text')
      if term:
        if len(result) + len(term.text) + 2 + example_len > limit:
          break
        examples.append(term.text)
        example_len += len(term.text) + 2
    if examples:
      result += ' (%s)' % '; '.join(examples)
    return result


class Entry(object):
  def __init__(self, type, terms):
    self.type = type
    self.terms = terms

  def getTerm(self, type):
    for term in self.terms:
      if term.type == type:
        return term
    return None

  def toString(self, indent=0):
    s = '%*s<Entry type=%s>\n' % (indent, '', self.type)
    for term in self.terms:
      s += term.toString(indent + 2)
    s += '%*s</Entry>\n' % (indent, '')
    return s


class Term(object):
  def __init__(self, type, language, text):
    self.type = type
    self.language = language
    self.text = text

  def toString(self, indent=0):
    return '%*s<Term type=%s language=%s>%s</Term>\n' % (indent, '', self.type, self.language, self.text)


def decode_object(dct):
  if 'type' in dct:
    if 'entries' in dct and 'terms' in dct:
      return PrimarySet(dct['type'], dct['entries'], dct['terms'])
    if 'terms' in dct:
      return Entry(dct['type'], dct['terms'])
    elif 'text' in dct and 'language' in dct:
      return Term(dct['type'], dct['language'], decode_text(dct['text']))
  return dct


def decode_text(text):
  return (text
      .replace('<b>', '\x02')
      .replace('</b>', '\x02'))


def preprocess_json(content):
  items = content.strip().rsplit(',', 2)
  if items:
    return re.sub(r'\\x([a-f\d][a-f\d])',
                  lambda x: chr(int(x.group(1), 16)),
                  items[0][2:])


def define(term, sl='en', tl='en'):
  fetcher = jsonfetch.Fetcher('http://www.google.com/dictionary/json',
                              callback='f',
                              q=term,
                              sl='en',
                              tl='en',
                              )
  data = fetcher.get(preprocessor=preprocess_json, object_hook=decode_object)
  if 'primaries' in data:
    return data['primaries'][0].renderAllDefinitions()
  else:
    return []


if __name__ == '__main__':
  import sys
  print '\n'.join(define(sys.argv[1]))
